--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════#
--║   RAIL COLLISSION HOOKS    ║ Functions related to objects touching the Rail hitboxes.
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════#
local RAIL = GS_RAILS if RAIL.blockload return end

--Account for hitbox differences and railswitching states!
--"so" is the source of the collision, likely a player mobj, but not always.
RAIL.RailHeightCheck = function(s, so, p)
	local topA = (s.GSrailtophitbox) or 0   --"Top Add" and "Bottom Add" vertical hitbox values.
	local botA = (s.GSrailbottomhitbox) or 0 --Auto-adjusting these values is necessary so jumping on steep rails isn't glitchy.	
	local RAIL = s.GSmainrail or s if RAIL.GSdisabled return false end
	if RAIL.GShangrail
		botA = $-(16<<16)
		if RAIL.GSinfo and ((RAIL.GSinfo.hangZoffset or 0) < 0)
			botA = $+(RAIL.GSinfo.hangZoffset<<16)
		end
	elseif so.z==so.floorz+2 or so.z==so.floorz
		topA,botA = $+8<<16, $-8<<16 --Bottom Hitboxes should be minus, Top Hitboxes should be plus.
	end
	local MOHEIGHT = so.height or 1
	local MOMZ = so.momz or 0
	if p
		if ((RAIL.GStouchflags or 0) & 65536) 
			return false --Player not allowed to touch it?
		elseif so.skin!="sms" --Muhahaha...
			if RAIL.GSonlyskin and (so.skin!=RAIL.GSonlyskin)
			or RAIL.GSnotskin and (so.skin==RAIL.GSnotskin)
				return false --
			end
		end	
		if p.moheight 
			MOHEIGHT = P_GetPlayerHeight(p) --idgaf if you're spinning, use your regular height here!
		end
		if ((so.flags2 & MF2_TWOD) or (twodlevel) or (so.currentaxis)) and not p.homing --In 2D, have less snap distance.
			topA,MOMZ = min($, 1),0
			botA, MOHEIGHT = $/2, $/2
		elseif p.homing or (so.eflags & MFE_SPRUNG) and (so.state!=S_PLAY_SPRING) --More generous when homing.		
			topA = $+(48*so.scale)+abs(MOMZ)
			botA = $-(48*so.scale)
		end
	end	
	local mobjz = so.z+MOMZ+(MOHEIGHT*2) -- Lazy shortcut.
	
	 --Be extra generous when railswitching! I don't want to mimmick SA2 and Heroes' unexpected death leaps
	if so.GSgrind and so.GSgrind.railswitching
		if ( s.z+botA > mobjz+(70*so.scale) ) --Rail is above 
		or ( so.z+MOMZ > s.z+topA+s.height+(85*so.scale) ) --Grinder is above
			return false --
		end
	elseif ( s.z+botA > mobjz ) --Rail is above
	or ( so.z+MOMZ > s.z+topA+s.height ) --Grinder is above
		return false --
	end
	return true --We made it past all the height checks, so return true!
end


--SafeType. If it doesn't exist, return 1 (MT_UNKNOWN). Because otherwise SRB2 makes it global and everyone dies.
RAIL.ST = function(ITEM)
	if (ITEM) and type(ITEM)=="number" return ITEM end
	return 7777
end local ST = RAIL.ST

--Most enemies can grind on rails, but here's a couple special objects. You can extend the list if you wish.
if RAIL.AttachTypes==nil RAIL.AttachTypes = {} end local A1 = RAIL.AttachTypes
A1[ST(MT_PLAYER)]		= true /*
A1[ST(MT_GARGOYLE)]		= true
A1[ST(MT_BIGGARGOYLE)]	= true
A1[ST(MT_EGGSTATUE)]  	= true
A1[ST(MT_EGGSTATUE2)] 	= true
A1[ST(MT_TURRET)]		= true
A1[ST(MT_MINECART)] 	= true
A1[ST(MT_DUSTDEVIL)]  	= true
A1[ST(MT_ROSY)]			= true */

--Never attach these objects to rails.
if RAIL.NeverAttachTypes==nil RAIL.NeverAttachTypes = {} end local A2 = RAIL.NeverAttachTypes /* 
A2[ST(MT_MINUS)]				= true
A2[ST(MT_DRAGONBOMBER)]			= true
A2[ST(MT_TELEPORTMAN)] 			= true
A2[ST(MT_OVERLAY)]				= true
A2[ST(MT_GSRAIL)] 				= true
A2[ST(MT_GSRAILSEG)] 			= true
A2[ST(MT_GSRAILDIRECTOR)]		= true
A2[ST(MT_GSRAILATTACHEDTHING)]	= true
A2[ST(MT_GSRAILGFX)]			= true
A2[ST(MT_GSRAILLIGHT)]			= true
A2[ST(MT_GSRAILSEG)] 			= true
A2[ST(MT_THOK)] 				= true
A2[ST(MT_RING)]					= true
A2[ST(MT_FLINGRING)]			= true
A2[ST(MT_SPINDUST)] 			= true
A2[ST(MT_BOXSPARKLE)]			= true
A2[ST(MT_IVSP)]					= true
A2[ST(MT_SUPERSPARK)] 			= true
A2[ST(MT_THUNDERCOIN_SPARK)] 	= true
A2[ST(MT_COIN)]					= true
A2[ST(MT_GHOST)]				= true
A2[ST(MT_UNKNOWN)] 				= true
A2[ST(MT_ROLLOUTROCK)] 			= true
A2[ST(MT_LAVAFALL)] 			= true
A2[ST(MT_LAVAFALL_LAVA)] 		= true
A2[ST(MT_FLAMEJETFLAMEB)] 		= true
A2[ST(MT_STARPOST)] 			= true
A2[ST(MT_WALLSPIKE)] 			= true
A2[ST(MT_SPIKE)] 				= true
A2[ST(MT_BUBBLES)] 				= true
A2[ST(MT_SMALLBUBBLE)] 			= true
A2[ST(MT_MEDIUMBUBBLE)] 		= true
A2[ST(MT_EXTRALARGEBUBBLE)] 	= true
for i = MT_RING_BOX,MT_RING_BLUEBOX
	A2[ST(i)] = true
end */

--We can attach to rails if....
RAIL.CanAttach = function(s, so)
--	if not (so and type(so)=="userdata" and userdataType(so)=="mobj_t" and so.valid)
	if not (so.player)
--	or (A2[so.type]) --Can never attach these objects!
--	or so.GScantgrind --Not in the GS table!
		return false --
	end
	local GS = so.GSgrind
	if GS and GS.cannotgrind return false end  --GS.cannotgrind is a temporary timer, while so.GScantgrind is permanent!
	
--	if so.player --The most obvious one.
		local p = so.player
		if (RAIL.busystates(so))
		or not (RAIL.RailHeightCheck(s, so, p))
		or (p.powers[pw_ignorelatch])
			return false --
		else
			if s.GSwalljumprail
				local WJ = (GS) and GS.walljump or nil
				if WJ
					if (WJ.lastwall==s.GSrailnum)
						return false --Can't re-attach to the same wall.
					end
				elseif not ( (p.cmd.buttons & BT_JUMP) and not (GS and ((GS.railjump or 0) > 98<<16)) )
					if not (p.pflags & (PF_JUMPED|PF_THOKKED|PF_STARTJUMP|PF_NOJUMPDAMAGE))
					or P_IsObjectOnGround(so)
						return false --Be in one of these states to attach and not grounded, or hold JUMP.
					end
				end
				if s.GSVertRailDOWNANG!=nil
					local INPUT = abs(p.cmd.sidemove)+abs(p.cmd.forwardmove)
					local SPED = ((INPUT<16) and 5 or 22)*s.scale
					if (abs(so.momx)+abs(so.momy) >= SPED) --If moving too slowly, skip angle check.
						local UPANG = s.GSVertRailDOWNANG+ANGLE_180
						local MOMANG = R_PointToAngle2(so.x, so.y, so.x+so.momx, so.y+so.momy)
						local ANGDIFF = RAIL.AngleDifference(UPANG, MOMANG, 2, ANG60, 2)
						if (ANGDIFF >= ANG30) 
							if (INPUT > 22) --Prioritize direction pressed angle
								MOMANG = (p.cmd.angleturn<<16)+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
								ANGDIFF = RAIL.AngleDifference(UPANG, MOMANG, 2, ANG60, 2)
								if (ANGDIFF > ANGLE_22h) return false end --
							else
								return false --
							end
						end
					end
				end
			elseif GS and GS.LTAB and GS.LTAB.timer and (GS.LTAB.nextrail==s.GSrailnum or GS.LTAB.rail==s.GSrailnum)
				return false --Don't attach to the same rail so soon!
			end
			return true --
		end
/*	else --It's an enemy, pushable or special type? Sure, let them join in on the fun too.
		if (A1[so.type]) --Special allowed types.
			if not (RAIL.RailHeightCheck(s, so)) and not s.GSVertRail --Only players can jump onto vertical rails instantly.
				return true --
			end
		else
			local FLAGS = so.flags or 0
			if (FLAGS & (MF_ENEMY|MF_BOSS)) --Enemies!
			or (FLAGS & MF_SOLID) and (FLAGS & MF_PUSHABLE) and not (FLAGS & (MF_SPECIAL|MF_NOSECTOR|MF_NOCLIPTHING|MF_AMBIENT))
				if not (RAIL.RailHeightCheck(s, so)) and not s.GSVertRail
					return true --
				end
			end
		end
	end */
	return false --
end

--Settings table that carries over between levels.
RAIL.SetupPlayerTable = function(p)
	p.GSRO = {} local RO = p.GSRO
	
	--Default Camera Menu settings.
	RO.cam_vanilla = false
	RO.cam_shift = 10 --Sideways camera shifting amount.
	RO.cam_dist = 192
	RO.cam_height = 40
	RO.cam_turnspeed = 100
	RO.cam_autoturnspeed = 75
	RO.cam_centerspeed = 150
	
	--Default Gameplay Menu settings
	RO.devmode = 0 
	RO.backupgrindstate = SPR2_ROLL
	RO.duncegrindstate = SPR2_EDGE
	RO.SideHold = 0
	RO.HUDaid = 1 
	
	--Notification timer.
	RO.NewbieGrinder = 600
	RAIL.SaveLoad(p, false, true)
end

--We succeeded colliding with the rail, so attach to it like so! s = snappoint, so = object attaching to the rail
RAIL.DoAttach = function(s, so, mainrail)
	local GS = so.GSgrind if GS==nil or GS.cannotgrind==nil RAIL.SetupMobjRailVars(so) GS=so.GSgrind end
	GS.cannotgrind = 2 --Stop further collission!
	if (so.flags2&MF2_TWOD) or (twodlevel) or so.currentaxis
		GS.TWOD = true --2D status includes Axis2D.
	else
		GS.TWOD = false
	end	
	
	local p = so.player --Usually a player, but always.
	if p and so.skin
		local SKIN = GS_RAILS_SKINS[so.skin]
		if SKIN and SKIN["PreAttach"] and SKIN["PreAttach"](p, so, GS, mainrail, s)==true 
			return false --
		end
	end
	
	local MOMANG = p and p.drawangle or so.angle
	if GS.railswitching and GS.railswitchANG!=nil and GS.TWOD!=true
		local ANGDIFF = RAIL.AngleDifference(GS.railswitchANG, RAIL.ANG(mainrail), 2, ANGLE_90, 2)
		if (ANGDIFF > ANGLE_45)
			MOMANG = mainrail.angle+ANGLE_180
		else
			MOMANG = mainrail.angle
		end
	elseif p and (p.speed < 13*so.scale) and ((abs(p.cmd.forwardmove)+abs(p.cmd.sidemove)) > 33) --Can override direction at low speeds.
		if GS.TWOD==true
			local SPED = max(p.speed+(2*so.scale), 11*so.scale)
			MOMANG = (p.cmd.sidemove < 0) and ANGLE_180 or 0 --180 is left. 0 is right.
			if (s.x > so.x) and MOMANG==ANGLE_180 --Rail's to our right yet our MOMANG is left? Nonono....
			or (s.x < so.x) and MOMANG==0 
				MOMANG = ANGLE_180
				SPED = $/3
			end	
			P_InstaThrust(so, MOMANG, SPED)
		else
			MOMANG = (p.cmd.angleturn<<16) + (R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))	
			P_InstaThrust(so, MOMANG, max(p.speed+(4*so.scale), 14*so.scale))
		end
	elseif so.momx or so.momy --Momentum angle before we started grinding? Angle yourself correctly or you lose speed!
		MOMANG = R_PointToAngle2(so.x, so.y, so.x+so.momx, so.y+so.momy)
	end
	GS.landangle = MOMANG
	
	GS.myrail = mainrail --These are sometimes the same as the snappoint, but not always.
	GS.grinding = 1 --Start grinding
	GS.rollangle,so.rollangle = 0,0
	GS.IgnoreConveyor = 2 --Ignore conveyor for a sec.
	
	local SPEED = abs(so.momx)+abs(so.momy)
	if mainrail.GSVertRail --Vertical rail snap?
		local LOOP = RAIL.LearnLoopType(mainrail)
		local TSPEED = so.momz
		if TSPEED 
			if not (LOOP and LOOP.walljump)
				if (TSPEED > 0) and (TSPEED < 26*s.scale) 
					if not (so.eflags & MFE_VERTICALFLIP)
						TSPEED = min(26*s.scale, $*2)
					end
				elseif (TSPEED < 0) and (TSPEED > -26*s.scale) and (so.eflags & MFE_VERTICALFLIP)
					TSPEED = max(-26*s.scale, $*2)
				end
			end
			if (SPEED > so.scale) TSPEED = FixedDiv(FixedMul($, 122+abs(SPEED/so.scale)), 121) end
		end
		if (TSPEED*P_MobjFlip(so) > so.scale) 
		or p and ((p.cmd.buttons & BT_JUMP) and (p.pflags & (PF_THOKKED|PF_GLIDING|PF_SPINNING)))
		or LOOP and LOOP.walljump
			TSPEED = $+ ((3*so.scale)*P_MobjFlip(so))
		end
		if p and (SPEED > so.scale)
			if (p.pflags & PF_THOKKED) or (p.cmd.buttons & BT_JUMP)
				local A,P = RAIL.AngleDifference(p.drawangle, R_PointToAngle2(so.x, so.y, so.x+so.momx, so.y+so.momy), 2, ANG60, 2)
				local ADD = max(20*so.scale, SPEED/2)
				if (A > ANG1*7)
					A = $/ANG1
					ADD = FixedDiv(FixedMul($, 7), 1+A)
				end
				if LOOP and LOOP.walljump
					ADD = $/2
				elseif ((p.cmd.buttons & BT_JUMP) or (p.pflags & PF_THOKKED)) and (p.cmd.forwardmove > 20) and (SPEED > 34*so.scale)
					ADD = ($*6/5)+so.scale
				end
				TSPEED = $+(ADD*P_MobjFlip(so))
			end
		end
		if (TSPEED < 0)
			GS.railspeed = (abs(TSPEED)*mainrail.GSVertRail)
			MOMANG = mainrail.GSVertRailDOWNANG
		else
			GS.railspeed = (-abs(TSPEED)*mainrail.GSVertRail) --print("\x81 2:\x84 "+TSPEED/FRACUNIT)
			MOMANG = mainrail.GSVertRailDOWNANG+ANGLE_180
		end
	--	if p p.drawangle = MOMANG else s.angle = MOMANG end
		GS.landangle = MOMANG
	else
		if GS.preswitchspeed and GS.railswitching
			GS.railspeed = GS.preswitchspeed+1 --(GS.preswitchspeed > 50*so.scale) and FixedDiv(FixedMul(GS.preswitchspeed, 70), 71) or
		else
			if ((SPEED+abs(s.momz)) > 36*so.scale) 
				if (so.skin!="sms") --How much speed?
					local DIV = (SPEED > 62*so.scale) and 2 or 1
					if (SPEED > 94*so.scale) and not (GS.railswitching)
						DIV = (SPEED > 158*so.scale) and 5 or (SPEED > 125*so.scale) and 4 or 3
						SPEED = max(94*so.scale, FixedDiv(FixedMul($, 100), 100+DIV) ) 
					else
						SPEED = max(30*so.scale, FixedDiv(FixedMul($, 100), 100+DIV) )  --Speed conversion formulas
					end
				end
			elseif SPEED
				SPEED = min(36*so.scale, $*6/5)
			end
			if (SPEED < 16*s.scale) and p and ((abs(p.cmd.sidemove)>22) or (abs(p.cmd.forwardmove)>22))
				SPEED = 16*s.scale --Minimum speed boost.
			end			
			GS.railspeed = max(so.scale, SPEED or 0)
		end	
		if so.momz and mainrail.GSrailZDelta --Landing on a sloped rail gives a big boost or slowdown!
			local ZANG = abs(mainrail.GSrailZDelta)
			if (ZANG >= ANG2)
				local MOMZ = max(-80*so.scale, min(80*so.scale, so.momz))
				MOMZ = FixedDiv(FixedMul($, 180), max(90, 180-(ZANG/ANG2)) )
				if GS.railswitching
					MOMZ = $/2
				end
				GS.zboost = (so.momz*P_MobjFlip(so) < -so.scale*9) and -MOMZ/2 or -MOMZ/3
			end
		end
	end
	GS.railsparks,GS.exittic,GS.leftrail,GS.MyRailStats,GS.spinstate,GS.ropesnap,GS.LTAB,GS.FORCEKICK = nil --delet1
	GS.forcetrailcolor,GS.speedtrail = nil
	
	if p
		if (p.pflags & PF_SPINNING) or (p.cmd.buttons & BT_SPIN) and (p.charability2==CA2_SPINDASH)
			if (mainrail.GSnorolling)
				p.pflags = $ & ~PF_SPINNING
			else
				GS.spinstate = true
			end
		end
		if GS.flipanim or GS.flip --Undo these properties!
			so.mirrored,so.spriteyoffset = false,0
		end	
		GS.specialflip,GS.flipanim,GS.flip,GS.flipanimCD,GS.dashmode,GS.normalspeed,GS.hanghop,GS.aimingreset = nil --delet2
		GS.switchCAMang,GS.nocam,GS.balancepenalties,GS.FreeAdd,GS.FuckingROLL,GS.forcemove,GS.debugE1,GS.viewrollangle,GS.VRplus = nil
		GS.crouchbutton,GS.twistbutton = 0,99<<16
		
		if (p.charflags & SF_DASHMODE) and p.dashmode --Record dash mode status for later.
			GS.dashmode,GS.normalspeed = p.dashmode,p.normalspeed
		end
		local snapZ = so.z+so.momz if not (mainrail.GSVertRail) snapZ = RAIL.GetRailZ(mainrail, so) end --Guess in advance for players.
		GS.LastGrindZ,GS.LastGrindZ2 = snapZ-so.momz, snapZ-(so.momz/2)

	--	p.panim = PA_ETC
	--	p.secondjump,p.glidetime,p.fly1,p.scoreadd,p.climbing,p.skidtime,p.homing = 0,0,0,0,0,0,0

		p.cmd.buttons,p.scoreadd,p.viewrollangle = 0,0,0
		p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~PF_CANCARRY
		p.powers[pw_nocontrol] = min($,2)
		p.powers[pw_carry] = 3888 --This is set mostly to make custom characters behave. Most characters account for pw_carry
		
		local A,P
		if GS.railswitching and GS.railswitchANG!=nil
			A,P = RAIL.AngleDifference(GS.railswitchANG, R_PointToAngle2(so.x, so.y, so.x+so.momx, so.y+so.momy), 2, ANG60, 2)	
		else
			A,P = RAIL.AngleDifference(p.drawangle, R_PointToAngle2(so.x, so.y, so.x+so.momx, so.y+so.momy), 2, ANG60, 2)	
		end
		if GS.stretch --Shouldn't be active at this point...
	 		so.spritexoffset,so.spriteyoffset = 0,0
			so.spritexscale,so.spriteyscale = FRACUNIT,FRACUNIT
		end	
		GS.airdrag,GS.hangswing,GS.ANGswap,GS.stretch,GS.railswitchANG,GS.animcall,GS.forcesprite,GS.curanim = nil --delet3
		GS.JumpRamp,GS.FrontiersLeap,GS.walljump,GS.holdF = nil --delet4
		
		if type(p.GSRO)!="table"
			RAIL.SetupPlayerTable(p) --Make this table if it doesn't exist yet. (Menu options table)
		end
		GS.invertinput = 1
		if (GS.TWOD!=true)
			local CAMANG = p.cmd.angleturn<<16
			if (GS.railcam and GS.railcam.valid) and p.awayviewtics and p.awayviewmobj==GS.railcam
				CAMANG = GS.railcam.angle
			end
			local ANGDIFF = RAIL.AngleDifference(CAMANG, MOMANG, 2, ANG1*89, 2)
			if (ANGDIFF > ANG1*69) --If the camera is backwards (69*2 = 138), invert the player's sideways inputs on some actions.
				GS.invertinput = -1
			end
		end
		GS.railtilt = GS.railtilt and $/2 or 0
		GS.camdir = P or 1

		if not (GS.railswitching or p.homing or mainrail.GSVertRail)
			GS.docam = nil
		else
			if GS.railswitching
				GS.docam = 3 --Keep camera active for a minimum of 3 frames.
				GS.camadd = (ANG10*-GS.camdir)
				GS.perfectcorner = 2
				if GS.railcam and GS.railcam.valid 
					GS.railcam.superturn = max(GS.railcam.superturn or 0, 4)
				end
			else
				GS.camadd = nil
				so.momz = 0
				if so.tracer and so.tracer.flags and (so.tracer.flags & (MF_ENEMY|MF_MONITOR|MF_SPRING|MF_BOSS))
					so.tracer = nil
				end
			end
			local snapX, snapY = RAIL.GetRailXYDistFromOrigin(so, rail)
			P_MoveOrigin(so, snapX, snapY, snapZ)
		end
		if (mainrail.GSloopflags & 8388608)
			if GS.railcam and GS.railcam.valid if p.awayviewmobj==GS.railcam p.awayviewmobj = nil end P_RemoveMobj(GS.railcam) end
			GS.docam = 0 --Nope, none of this!
		end
		if not (so.eflags & MFE_VERTICALFLIP) and mainrail.GShangrail
		or (so.eflags & MFE_VERTICALFLIP) and not mainrail.GShangrail
			so.state = S_PLAY_RIDE
		else
			so.state = S_PLAY_GSGRINDING
		end
	else
		GS.LastGrindZ,GS.LastGrindZ2 = so.z-so.momz, so.z-(so.momz/2)
	end
	GS.railswitching = 0
end

--Collide with a rail to attach and go into a grinding state!
RAIL.GrindRailCollide = function(s, so)
	if not RAIL.CanAttach(s, so)
		return false --
	elseif not (s.GSnextrail and s.GSnextrail.valid)
		if so.player and s.GSprevrail and (so.player.speed > so.scale) and s.GSprevrail.valid 
		and (RAIL.AngleDifference(s.GSprevrail.angle+ANGLE_180, R_PointToAngle2(so.x, so.y, so.x+so.momx, so.y+so.momy), 2, ANGLE_90, 2) >= ANGLE_45)
			RAIL.DoAttach(s.GSprevrail, so, s.GSprevrail)
		end
	else
		RAIL.DoAttach(s, so, s) --We touched the rail! Start grindrail behaviour!
	end return false --
end

--If colliding with a segment, do similar behaviour, but refer back to the main rail!
RAIL.GrindSegmentCollide = function(s, so)
	if not RAIL.CanAttach(s, so) 
	or not (s.GSmainrail and s.GSmainrail.valid and s.GSmainrail.GSnextrail and s.GSmainrail.GSnextrail.valid)
		return false --
	else
		RAIL.DoAttach(s, so, s.GSmainrail) return false --Pass it onto the main rail, but make us the snappoint!
	end
end

--Take over the function of springs/boosters for custom behaviour and ignore teleportation behaviour.
--"AttachedRailThingCollide"! This is the collission hook, btw. For the actual action, look one lower.
RAIL.RailObjectCollide = function(s, so, FORCE)
	if not (type(so)=="userdata" and so.valid and so.player) return false end --Valid toucher, pls.
	
	local GS, p = so.GSgrind, so.player
	if GS==nil or GS.cannotgrind==nil RAIL.SetupMobjRailVars(so) GS=so.GSgrind end
	
	if GS and GS.grinding --Already grinding?
		if (s.z > so.z+so.momz+so.height+(64*so.scale)) or (so.z+so.momz > s.z+s.height+(64*so.scale))
			if (FORCE!=true) return false end
		end
	elseif not p
	or (s.z > so.z+so.momz+so.height) or (so.z+so.momz > s.z+s.height) --Height checks, yadayada...
		return false --
	end	
	if not (GS and (GS.railobject or GS.justtouchedrailthing))
		if s.tracer and s.tracer.valid
			if GS and GS.grinding
				if (s.tracer.flags & MF_MONITOR)
					if s.tracer.health or (s.tracer.flags & MF_SHOOTABLE)
						P_DamageMobj(s.tracer, so, so, 1)
					end 
					return false --
				else
					so.GScollide = s.tracer --Refer them back to the OG object. Collide hooks are too unstable.
					GS.justtouchedrailthing = (s.tracer.scale > s.scale) and 7 or 5
					s.tracer.flags = $|MF_NOCLIPTHING
				end
			elseif not (so.GScollide==s.tracer and GS and GS.justtouchedrailthing) --Refer back to the OG object in this case.
				s.tracer.flags = $ & ~MF_NOCLIPTHING --Wow, you're touchable again, it's miracle!
				if p and p.homing --Transition snuggly to the rail from your homing attack.
					if (GS!=nil)
						GS.justtouchedrailthing = 0
						if (s.tracer.flags & MF_MONITOR)
							GS.cannotgrind = max(GS.cannotgrind or 0,3)
						end
					end
					p.homing = min($,15)
				end
			end
		elseif s.valid
			P_RemoveMobj(s) return false --Remove the tracker and let vanilla SRB2 handle colission!
		end 
	end return false --
end

--A mobj collided with a special rail-attached item!
RAIL.AttachedCollission = function(s, rail, GS)
	if GS==nil or GS.cannotgrind==nil RAIL.SetupMobjRailVars(s) GS=s.GSgrind end
	local t = s.GScollide if not (t and t.valid) return end --
	if (t.flags & MF_MONITOR) --...huh! Simple enough.
		if GS.justtouchedrailthing
			GS.justtouchedrailthing = 0
			s.GScollide = nil
		end
		P_DamageMobj(t, s, s, 1) return true --
	end
	local p = s.player
	local SPEED,MOMANG = 0,s.angle
	local TYPE = RAIL.AttachList[t.type]	
	local BASHIT = false
	if GS
		if GS.railspeed
			SPEED = abs(GS.railspeed)
		else
			GS.railspeed = 0
		end
		if GS.raildirection!=nil and GS.myrail
			MOMANG = GS.myrail.angle+GS.raildirection
		end
		if p and (p.powers[pw_super] or p.hypermysticsonic or p.powers[pw_invulnerability]
		or p.xsonictable and p.xsonictable.darkphase==3)
			BASHIT = true
		elseif TYPE=="damage" or TYPE=="spike"
			if (SPEED > 3*s.scale) and not (s.flags & (MF_NOCLIP|MF_NOCLIPTHING))
				GS.railspeed = -$/9
				SPEED = abs(GS.railspeed)
			end
		end	
	end	
	if (t.flags & MF_SPRING) or TYPE=="booster"
		if t.info.raisestate != S_NULL --Switch the spring's state.
			t.state = t.info.raisestate
		end
		if t.info.painsound and (t.state!=t.info.raisestate)
			S_StartSound(s, t.info.painsound) --Play the spring sound.
		end
		if GS and GS.grinding --Try snapping to the object's middle if it's a spring/booster.
			if (P_TryMove(s, t.x, t.y, true))
				if (abs(t.z-s.z) < 99<<16) s.z = t.z end
			end
		end
		if not t.info.mass or TYPE=="booster" --Horizontal spring/booster!
			if t.info.damage
				local PreSpeed, SameDirection = SPEED, false
				RAIL.MobjToRailAngle(s, rail or GS.myrail or s, t.angle, false)
				if (SPEED >= 0) and (PreSpeed >= 0) or (SPEED < 0) and (PreSpeed < 0)
					SameDirection = true
				end
				local RSPEED = abs(SPEED)
				local POWER = FixedDiv(FixedMul(t.info.damage or FRACUNIT, max(64, t.scale)), FRACUNIT) --Scale the spring power!
				if (POWER > RSPEED) or (SameDirection!=true)
					GS.railspeed = ($ < 0) and -POWER or POWER
					if (SameDirection!=true) and p
						if GS.camdir GS.camdir = -$ end
						if GS.camadd GS.camadd = -$ end

						if GS.railcam and GS.railcam.valid
						and not (GS.myrail and GS.myrail.GSVertRail) and (GS.TWOD!=true)
							GS.railcam.superturn = 12 --Turn faster.
							if GS.camdir
								GS.railcam.angle = $+(ANGLE_45*GS.camdir)
							end
						end
					end
					RSPEED = abs(GS.railspeed)
				elseif SameDirection 
					if (RSPEED < 220*s.scale) --Very minor boost rather than resetting your speed which'd suck.
						GS.railspeed = (RSPEED < 58*s.scale) and $+(t.scale*2) or $+(t.scale/4)
						RSPEED = abs(GS.railspeed)
					end
				end
				if (GS.myrail and GS.myrail.GSVertRail) and (GS.TWOD!=true) and p
					p.drawangle = t.angle
					p.aiming = $/4
				else
					s.angle = t.angle
				end
				if GS.railtilt
					if p
						GS.badbalance = false
						GS.perfectcorner = max(GS.perfectcorner or 0, 4)
					end
					GS.IgnoreConveyor = max(IgnoreConveyor or 0, 2)
					GS.railtilt,GS.rollangle = 0,0 --Rebalance
					s.rollangle,s.spritexoffset,s.spriteyoffset = 0,0,0
				end
				GS.zboost = nil --Overwrite landing boost.
				if (RSPEED < 54*s.scale) --Try not to go THIS ridiculously slow!
					if (RSPEED < 33*s.scale)
						GS.justtouchedrailthing = max(GS.justtouchedrailthing or 0, 9)
					else
						GS.justtouchedrailthing = max(GS.justtouchedrailthing or 0, 6)
					end
				end
			end
		else --Diagional or vertical spring! Detach from rail! Balloons and bumpers count too.
			if t.info.damage
			or (t.type >= MT_FAN) and (t.type <= MT_BALLOON) and (t.type!=MT_BUMPER)
				RAIL.ExitRail(s, true, true) --Keep momentum.
				if t.info.damage
					P_InstaThrust(s, t.angle, FixedDiv(FixedMul(t.info.damage, t.scale), FRACUNIT) )
					s.angle = t.angle if p p.drawangle = t.angle end
				end
			else
				RAIL.ExitRail(s, false, true) --Strictly use spring momentum!
			end
			if t.info.mass
				s.z = $+(48*P_MobjFlip(t))
				s.momz = FixedDiv(FixedMul(t.info.mass, t.scale), FRACUNIT)*P_MobjFlip(t)
			end
			if p --Some player specific stuff.
				p.powers[pw_nocontrol] = max($,3)
				if (p.panim >= PA_IDLE) and (p.panim <= PA_FALL) or RAIL.GrindState(s, "any")
					s.state = S_PLAY_SPRING
					s.eflags = $|MFE_SPRUNG
				end
				if s.skin == "adventuresonic" and p.yusonictable --Trixx are for (cool) kidz
					local yu = p.yusonictable
					if (t.type == MT_BALLOON) or (t.type == MT_BUMPER)
						s.state = S_PLAY_ROLL
						yu.noairdrag2 = max(yu.noairdrag2 or 0, 6)
						p.pflags = $|PF_JUMPED & ~(PF_THOKKED|PF_NOJUMPDAMAGE|PF_STARTJUMP|PF_SPINNING)
						if not yu.supersonic
							yu.homingtrick, yu.chosentrick, yu.lasttrick = 40, 200, 2
						end
						yu.sa2jumpdone,yu.airborne = true,30	
					end
					yu.hitspring = 2
				end
			end
			if GS
				GS.cannotgrind = max(GS.cannotgrind or 0, 7)
			end
			if not (t.info.mass) and SPEED
				if (t.type!=MT_BUMPER) and (t.type!=MT_STEAM) and (TYPE!="dislodge")
					P_InstaThrust(s, MOMANG, SPEED*2/3) return --
				end
			end
		end
	end
	if TYPE == "dislodge" 
		if GS
			GS.cannotgrind = max(GS.cannotgrind or 0, 7)
		end
		s.eflags = $|MFE_SPRUNG
		RAIL.ExitRail(s, false, true)
		if s
			if (t.type==MT_NIGHTSBUMPER)	
				if p
					if SPEED P_Thrust(s, MOMANG, SPEED/9) end
					P_MoveOrigin(s, t.x,t.y,t.z)
					P_SetObjectMomZ(s, 3<<16, true)
					p.bumpertime = 0
					p.powers[pw_ignorelatch] = 0
					if GS GS.cannotgrind = max($, 14) end
					s.angle,p.drawangle = MOMANG,MOMANG
				end
			elseif SPEED and (t.flags & MF_SPRING) and (t.type!=MT_BUMPER)
				P_TryMove(s, t.x, t.y, true)
				P_InstaThrust(s, MOMANG, SPEED*2/3)
			end
		end
	elseif TYPE == "damage" or TYPE=="spike"
		if t.health or (t.flags & MF_SHOOTABLE)
			if ((t.type==MT_TNTBARREL) or (t.type==MT_WALLSPIKE)) and BASHIT!=true
				P_DamageMobj(s, t, t) --This one explodes instantly!
			end
			if BASHIT==true or (t.type==MT_TNTBARREL)
				if (t.health<=1) or t.health==1000
					P_KillMobj(t, s, s)
				else
					P_DamageMobj(t, s, s, 1)
				end
			end
		end
	end
end

--When colliding with a rail and allowed to attach, give rail properties to the mobj.
--I'll put most mobj related variables into the player.mo.GSgrind table. (case sensitive!)
RAIL.SetupMobjRailVars = function(s)
	if not (type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid) return end
	local GS = s.GSgrind if GS==nil s.GSgrind = {} GS=s.GSgrind end
	if GS.grinding == nil
		GS.grinding,GS.grindsfx,GS.grindsfx2 = 0,0,0
	end
	if GS.cannotgrind == nil
		GS.cannotgrind = 0
	end
	if GS.reversing==nil --Convenience feature to tell if a mobj is reversing on a rail or not.
		GS.reversing = 1
	end
	if s.player --Players get the more complex stuff.
		GS.railfalltimer,GS.railtilt,GS.railswitching = 0,0,0
		GS.railjump,GS.railspin,GS.railtossflag,GS.railc1,GS.railc2,GS.railc3 = 99<<16,99<<16,99<<16,99<<16,99<<16,99<<16
		GS.railWEAPPREV,GS.railWEAPNEXT, GS.railfire, GS.railnormalfire = 99<<16,99<<16,99<<16,99<<16
		GS.twistbutton,GS.crouchbutton = 99<<16,0
		if GS.backupjump==nil or GS.backupspin==nil
			GS.backupjump,GS.backupspin = 0,0
		end
		if GS.invertinput==nil
			GS.invertinput = 1
		end
		if GS.TWOD==nil
			GS.TWOD = ((s.flags2 & MF2_TWOD) or (twodlevel) or s.currentaxis) and true or false
		end
		GS.badbalance = false
		if GS.rollangle == nil
			GS.rollangle = 0
		end
		if type(s.player.GSRO)!="table"
			RAIL.SetupPlayerTable(s.player)
		end
	end
	--Do NOT override these next variables if they already exist!
	if GS.grinddirection == nil
		if GS.myrail and GS.myrail.valid
			GS.grinddirection = (RAIL.ANG(GS.myrail))+ANGLE_180
		elseif not s.player and (s.GSmyrail and s.GSmyrail.valid) --Backup variable for some objects.
			GS.grinddirection = (RAIL.ANG(s.GSmyrail))+ANGLE_180
		else
			if ((abs(s.momx)+abs(s.momy)) > (9*max(FRACUNIT, s.scale)))
				GS.grinddirection = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
			else
				GS.grinddirection = s.player and s.player.drawangle or s.angle
			end
		end
	end
	if GS.raildirection == nil
		GS.raildirection = 0
	end
	if GS.LastGrindZ == nil
		GS.LastGrindZ = s.z-s.momz
		GS.LastGrindZ2 = nil
	end
	if GS.railspeed == nil
		GS.railspeed = 0
	end	
	if GS.railboost == nil
		GS.railboost = 0
	end
end